/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-10
 * V4.0
 */
package com.jphenix.clazz;

import com.jphenix.standard.docs.ClassInfo;


/**
 * A support class for dealing with descriptors.
 *
 * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)"
 * @author mabg
 */
@ClassInfo({"2014-06-10 19:55","A support class for dealing with descriptors"})
public class Descriptor {
    
    /**
     * Converts a class name into the internal representation used in
     * the JVM.
     *
     * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent
     * to <code>toJvmName(s)</code>.
     */
    public static String toJvmName(String classname) {
        return classname.replace('.', '/');
    }

    /**
     * Converts a class name from the internal representation used in
     * the JVM to the normal one used in Java.
     * This method does not deal with an array type name such as
     * "[Ljava/lang/Object;" and "[I;".  For such names, use
     * <code>toClassName()</code>.
     *
     * @see #toClassName(String)
     */
    public static String toJavaName(String classname) {
        return classname.replace('/', '.');
    }


    /**
     * Converts to a Java class name from a descriptor.
     *
     * @param descriptor        type descriptor.
     */
    public static String toClassName(String descriptor) {
        int arrayDim = 0;
        int i = 0;
        char c = descriptor.charAt(0);
        while (c == '[') {
            ++arrayDim;
            c = descriptor.charAt(++i);
        }

        String name;
        if (c == 'L') {
            int i2 = descriptor.indexOf(';', i++);
            name = descriptor.substring(i, i2).replace('/', '.');
            i = i2;
        }
        else if (c == 'V') {
            name =  "void";
        } else if (c == 'I') {
            name = "int";
        } else if (c == 'B') {
            name = "byte";
        } else if (c == 'J') {
            name = "long";
        } else if (c == 'D') {
            name = "double";
        } else if (c == 'F') {
            name = "float";
        } else if (c == 'C') {
            name = "char";
        } else if (c == 'S') {
            name = "short";
        } else if (c == 'Z') {
            name = "boolean";
        } else {
            throw new RuntimeException("bad descriptor: " + descriptor);
        }

        if (i + 1 != descriptor.length()) {
            throw new RuntimeException("multiple descriptors?: " + descriptor);
        }

        if (arrayDim == 0) {
            return name;
        } else {
            StringBuffer sbuf = new StringBuffer(name);
            do {
                sbuf.append("[]");
            } while (--arrayDim > 0);

            return sbuf.toString();
        }
    }

    /**
     * Converts to a descriptor from a Java class name
     */
    public static String of(String classname) {
        if ("void".equals(classname)) {
            return "V";
        } else if ("int".equals(classname)) {
            return "I";
        } else if ("byte".equals(classname)) {
            return "B";
        } else if ("long".equals(classname)) {
            return "J";
        } else if ("double".equals(classname)) {
            return "D";
        } else if ("float".equals(classname)) {
            return "F";
        } else if ("char".equals(classname)) {
            return "C";
        } else if ("short".equals(classname)) {
            return "S";
        } else if ("boolean".equals(classname)) {
            return "Z";
        } else {
            return "L" + toJvmName(classname) + ";";
        }
    }


    /**
     * Returns true if the list of the parameter types of desc1 is equal to
     * that of desc2.
     * For example, "(II)V" and "(II)I" are equal.
     */
    public static boolean eqParamTypes(String desc1, String desc2) {
        if (desc1.charAt(0) != '(') {
            return false;
        }
        for (int i = 0; true; ++i) {
            char c = desc1.charAt(i);
            if (c != desc2.charAt(i)) {
                return false;
            }
            if (c == ')') {
                return true;
            }
        }
    }

    /**
     * Returns the signature of the given descriptor.  The signature does
     * not include the return type.  For example, the signature of "(I)V"
     * is "(I)".
     */
    public static String getParamDescriptor(String decl) {
        return decl.substring(0, decl.indexOf(')') + 1);
    }


    /**
     * Returns the number of the prameters included in the given
     * descriptor.
     *
     * @param desc descriptor
     */
    public static int numOfParameters(String desc) {
        int n = 0;
        int i = 1;
        for (;;) {
            char c = desc.charAt(i);
            if (c == ')') {
                break;
            }

            while (c == '[') {
                c = desc.charAt(++i);
            }

            if (c == 'L') {
                i = desc.indexOf(';', i) + 1;
                if (i <= 0) {
                    throw new IndexOutOfBoundsException("bad descriptor");
                }
            }
            else {
                ++i;
            }

            ++n;
        }

        return n;
    }


    /**
     * Computes the dimension of the array represented by the given
     * descriptor.  For example, if the descriptor is <code>"[[I"</code>,
     * then this method returns 2.
     *
     * @param desc the descriptor.
     * @return 0        if the descriptor does not represent an array type.
     */
    public static int arrayDimension(String desc) {
        int dim = 0;
        while (desc.charAt(dim) == '[') {
            ++dim;
        }

        return dim;
    }

    /**
     * Returns the descriptor of the type of the array component.
     * For example, if the given descriptor is
     * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2,
     * then this method returns <code>"Ljava/lang/String;"</code>.
     *
     * @param desc the descriptor.
     * @param dim  the array dimension.
     */
    public static String toArrayComponent(String desc, int dim) {
        return desc.substring(dim);
    }

    /**
     * Computes the data size specified by the given descriptor.
     * For example, if the descriptor is "D", this method returns 2.
     *
     * <p>If the descriptor represents a method type, this method returns
     * (the size of the returned value) - (the sum of the data sizes
     * of all the parameters).  For example, if the descriptor is
     * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1).
     *
     * @param desc descriptor
     */
    public static int dataSize(String desc) {
        return dataSize(desc, true);
    }

    /**
     * Computes the data size of parameters.
     * If one of the parameters is double type, the size of that parameter
     * is 2 words.  For example, if the given descriptor is
     *  <code>"(IJ)D"</code>, then this method returns 3.  The size of the
     * return type is not computed.
     * 
     * @param desc      a method descriptor.
     */
    public static int paramSize(String desc) {
        return -dataSize(desc, false);
    }

    private static int dataSize(String desc, boolean withRet) {
        int n = 0;
        char c = desc.charAt(0);
        if (c == '(') {
            int i = 1;
            for (;;) {
                c = desc.charAt(i);
                if (c == ')') {
                    c = desc.charAt(i + 1);
                    break;
                }

                boolean array = false;
                while (c == '[') {
                    array = true;
                    c = desc.charAt(++i);
                }

                if (c == 'L') {
                    i = desc.indexOf(';', i) + 1;
                    if (i <= 0) {
                        throw new IndexOutOfBoundsException("bad descriptor");
                    }
                }
                else {
                    ++i;
                }

                if (!array && (c == 'J' || c == 'D')) {
                    n -= 2;
                } else {
                    --n;
                }
            }
        }

        if (withRet) {
            if (c == 'J' || c == 'D') {
                n += 2;
            } else if (c != 'V') {
                ++n;
            }
        }

        return n;
    }


    /**
     * An Iterator over a descriptor.
     */
    public static class Iterator {
        private String desc;
        private int index, curPos;
        private boolean param;

        /**
         * Constructs an iterator.
         *
         * @param s         descriptor.
         */
        public Iterator(String s) {
            desc = s;
            index = curPos = 0;
            param = false;
        }

        /**
         * Returns true if the iteration has more elements.
         */
        public boolean hasNext() {
            return index < desc.length();
        }

        /**
         * Returns true if the current element is a parameter type.
         */
        public boolean isParameter() { return param; }

        /**
         * Returns the first character of the current element.
         */
        public char currentChar() { return desc.charAt(curPos); }

        /**
         * Returns true if the current element is double or long type.
         */
        public boolean is2byte() {
            char c = currentChar();
            return c == 'D' || c == 'J';
        }

        /**
         * Returns the position of the next type character.
         * That type character becomes a new current element.
         */
        public int next() {
            int nextPos = index;
            char c = desc.charAt(nextPos);
            if (c == '(') {
                ++index;
                c = desc.charAt(++nextPos);
                param = true;
            }

            if (c == ')') {
                ++index;
                c = desc.charAt(++nextPos);
                param = false;
            }

            while (c == '[') {
                c = desc.charAt(++nextPos);
            }

            if (c == 'L') {
                nextPos = desc.indexOf(';', nextPos) + 1;
                if (nextPos <= 0) {
                    throw new IndexOutOfBoundsException("bad descriptor");
                }
            }
            else {
                ++nextPos;
            }

            curPos = index;
            index = nextPos;
            return curPos;
        }
    }
}
